import { Logger } from '@milkdown/dev/logger'
import { execSync } from 'child_process'
import { appendFileSync, readdirSync, existsSync } from 'fs'
import { join, basename } from 'path'

const logger = new Logger('changelog')

const commitTypes = [
  'feat',
  'fix',
  'chore',
  'docs',
  'style',
  'refactor',
  'perf',
  'test',
  'build',
  'ci',
  'other',
]

const commits = {}
commitTypes.forEach((type) => {
  commits[type] = []
})

const processLogChunk = (log: string) => {
  const logLines = log.split('\n')

  for (const line of logLines) {
    if (line.includes('chore: version packages')) {
      return false
    }

    const match = line.match(/^(\w+)(?:\(.+\))?:\s/)

    if (match && commitTypes.includes(match[1])) {
      commits[match[1]].push(line)
    } else {
      commits['other'].push(line)
    }
  }

  return true
}

const readGitLogInChunks = async (chunkSize = 20) => {
  let skip = 0
  let moreLogs = true

  while (moreLogs) {
    const logChunk = execSync(
      `git --no-pager log --pretty=format:"%s" --skip=${skip} -n ${chunkSize}`,
      {
        encoding: 'utf-8',
      }
    )

    if (!logChunk) {
      break
    }

    moreLogs = processLogChunk(logChunk)
    skip += chunkSize
  }
}

const generateMarkdown = (commitGroups: Record<string, string[]>) => {
  let markdown = ''

  for (const [type, messages] of Object.entries(commitGroups)) {
    if (messages.length > 0) {
      const capitalizedType = type.charAt(0).toUpperCase() + type.slice(1)
      markdown += `## ${capitalizedType}\n\n`
      messages.forEach((msg) => {
        markdown += `- ${msg}\n`
      })
      markdown += '\n'
    }
  }

  return markdown
}

const getChangesetFile = () => {
  const filePattern = /^[a-z]+-[a-z]+-[a-z]+\.md$/
  const gitRoot = execSync('git rev-parse --show-toplevel', {
    encoding: 'utf-8',
  }).trim()
  const changesetDir = join(gitRoot, '.changeset')

  if (!existsSync(changesetDir)) {
    logger.error('The .changeset directory does not exist.')
    process.exit(1)
  }

  const files = readdirSync(changesetDir)
  const targetFile = files.find((file) => filePattern.test(file))

  return targetFile ? join(changesetDir, targetFile) : null
}

readGitLogInChunks()
  .then(() => {
    const changesetFilePath = getChangesetFile()
    const markdownContent = generateMarkdown(commits)

    if (!changesetFilePath) {
      logger.warn('No changeset file found. Outputting to console instead.')
      logger.log(markdownContent)
      process.exit(1)
    } else {
      // Prepend a newline before the markdown content
      appendFileSync(changesetFilePath, '\n' + markdownContent)

      logger.info(
        `Changelog has been appended to ${basename(changesetFilePath)}`
      )
    }
  })
  .catch((err) => {
    logger.error('An error occurred while processing the git log.', err)
    process.exit(1)
  })
